# 第9章 递归

# 概念

递归是一种解决问题的方法,他从解决问题的各个小部分开始,直到解决最初的大问题。递归通常涉及函数调用自身。

# 计算阶乘

function factorialIterative(number) {
  if (number < 0) {
    return undefined
  }
  let total = 1
  for (let n = number; n > 1; n--) {
    total *= n
  }
  return total
}

function factorial(n) {
  if (n < 0) {
    return undefined
  }
  if (n === 1 || n === 0) {
    return 1
  }
  return n * factorial(n - 1)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 斐波那契数列

//斐波那契数列
function fibonacci(n) {
  if (n < 1) {
    return 0
  }
  if (n <= 2) {
    return 1
  }
  return fibonacci(n - 1) + fibonacci(n - 2)
}
//迭代
function fibonacciIterative(n) {
  if (n < 1) {
    return 0
  }
  let fibNMinus2 = 0
  let fibNMinus1 = 1
  let fibN = n
  for (let i = 2; i <= n; i++) {
    fibN = fibNMinus1 + fibNMinus2
    fibNMinus2 = fibNMinus1
    fibNMinus1 = fibN
  }
  return fibN
}
//记忆化斐波那契数列
//记忆化是一种保存前一个结果的值的优化技术,类似于缓存。
function fibonacciMemoization(n) {
  if (n < 1) {
    return 0
  }
  const memo = [0, 1]
  const fibonacciMem = num => {
    if (memo[num] != null) {
      return memo[num]
    }
    return (memo[num] = fibonacciMem(num - 1) + fibonacciMem(num - 2))
  }
  return fibonacciMem(n)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 尾调用优化

es6 中有尾调用优化(tail call),如果函数内的最后一个操作是调用函数,会通过‘跳转指令’而不是‘子程序调用’来控制。也就是说,在 es6 中代码会一直执行下去。因此,具有停止递归的基线条件非常重要。

function Fibonacci(n, ac1 = 1, ac2 = 1) {
  if (n <= 1) {
    return ac2
  }

  return Fibonacci(n - 1, ac2, ac1 + ac2)
}
1
2
3
4
5
6
7

迭代版本比递归的版本要快的多,所以这表示递归更慢。但是递归版本更容易理解,需要的代码通常也更少。另外,对一些算法来说,迭代的解法可能不可用,而且有了尾调用优化,递归的多余消耗甚至可能被消除。